package cn.xo68.boot.auth.server.shiro.realm;

import cn.xo68.boot.auth.core.domain.OAuth2AuthenticationToken;
import cn.xo68.boot.auth.core.domain.Oauth2Principal;
import cn.xo68.boot.auth.core.properties.OAuthResourceProperties;
import cn.xo68.boot.auth.server.domain.OauthResource;
import cn.xo68.boot.auth.server.domain.OauthToken;
import cn.xo68.boot.auth.server.domain.OauthUser;
import cn.xo68.boot.auth.server.properties.AuthServerProperties;
import cn.xo68.boot.auth.server.service.*;
import cn.xo68.core.date.DateTime;
import cn.xo68.core.util.StringTools;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;

import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * oauth2 服务端自定义认证实现
 * @author wuxie
 * @date 2018-8-5
 */
public class OAuth2ServerAuthorizingRealm extends AuthorizingRealm {

    private AuthServerProperties authServerProperties;
    private OAuthResourceProperties oAuthResourceProperties;

    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    @Lazy
    @Autowired
    private OauthTokenService oauthTokenService;

    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    @Lazy
    @Autowired
    private OauthUserService oauthUserService;

    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    @Lazy
    @Autowired
    private OauthUserRoleService oauthUserRoleService;
    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    @Lazy
    @Autowired
    private OauthResourceService oauthResourceService;

    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    @Lazy
    @Autowired
    private OauthRoleResourceService oauthRoleResourceService;

    public AuthServerProperties getAuthServerProperties() {
        return authServerProperties;
    }

    public void setAuthServerProperties(AuthServerProperties authServerProperties) {
        this.authServerProperties = authServerProperties;
    }

    public OAuthResourceProperties getoAuthResourceProperties() {
        return oAuthResourceProperties;
    }

    public void setoAuthResourceProperties(OAuthResourceProperties oAuthResourceProperties) {
        this.oAuthResourceProperties = oAuthResourceProperties;
    }

    @Override
    public boolean supports(AuthenticationToken token) {
        //表示此Realm支持:UsernamePasswordToken,OAuth2AuthenticationToken
        return token instanceof OAuth2AuthenticationToken;
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        Object obj=  principals.getPrimaryPrincipal();
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        if(obj!=null && obj instanceof Oauth2Principal ){
            Oauth2Principal oauth2Principal=(Oauth2Principal)obj;
            if(oauth2Principal.getRoles()!=null && !oauth2Principal.getRoles().isEmpty()){
                authorizationInfo.addRoles(oauth2Principal.getRoles());
            }
            //取用户资源
            List<OauthResource> appResources = oauthResourceService.list(oAuthResourceProperties.getClientId());

            final Set<String> userResourceIds=new HashSet<>();

            List<String> roles= oauth2Principal.getRoles();
            roles.forEach(roleId -> {
                userResourceIds.addAll(oauthRoleResourceService.listRoleResourcs(roleId, oAuthResourceProperties.getClientId()));
            });
            List<OauthResource> userResources=appResources.stream().filter(oauthResource -> {
                return userResourceIds.contains(oauthResource.getResourceId());
            }).collect(Collectors.toList());
            if(userResources.size()>0){
                userResources.forEach(oauthResource -> {
                    if(StringTools.isNotEmpty(oauthResource.getPermission())){
                        authorizationInfo.addStringPermission(oauthResource.getPermission());
                    }
                });

            }
        }
        //authorizationInfo.addStringPermission("sys:info");
        return authorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        OAuth2AuthenticationToken oAuth2Token = (OAuth2AuthenticationToken) token;
        Oauth2Principal oauth2Principal = oAuth2Token.getPrincipal();
        if(oauth2Principal==null){
            throw new AuthenticationException("请求不合法,errorCode:001");
        }

        //服务端直接读取redis
        OauthToken oauthToken=oauthTokenService.get(oAuth2Token.getCredentials());
        if(oauthToken==null){
            throw new AuthenticationException("请求不合法,errorCode:002");
        }
        if(validateTokenExpire(oauthToken.getExpireTime())){
            throw new AuthenticationException("请求不合法,errorCode:003");
        }


        OauthUser oauth2User=oauthUserService.getByUserId(oauthToken.getUserId());
        if(oauth2User==null){
            throw new AuthenticationException("请求不合法,errorCode:004");
        }
        BeanUtils.copyProperties(oauth2User, oauth2Principal, "userPass","createTime","lastLoginTime","loginCount","userLock");

        List<String> roles = oauthUserRoleService.getUserRoles(oauth2Principal.getUserId());

        oauth2Principal.addRoles(roles);

        SimpleAuthenticationInfo authenticationInfo =
                new SimpleAuthenticationInfo(oauth2Principal, oAuth2Token.getCredentials(), getName());
        return authenticationInfo;
    }

    /**
     * 验证令牌过期时间
     * @param expireDate
     * @return true：没有过期，反之过期
     */
    private boolean validateTokenExpire(Date expireDate){
        if(expireDate.getTime() < DateTime.Now().getTime()){
            return true;
        }else {
            return false;
        }
    }

}